from dataclasses import dataclass
import json


@dataclass
class JsonDiffContent:
    current_node: str
    type: str

    def __init__(self, type: str, path_nodes: list[str]):
        self.current_node = self.get_path(path_nodes)
        self.type = type

    def get_path(self, path_nodes: list[str]) -> str:
        current_node = '/'.join(path_nodes)
        if not current_node.startswith('/'):
            current_node = '/' + current_node
        return current_node


@dataclass
class KeysDiff(JsonDiffContent):
    first_more: dict
    second_more: dict
    first_ignored: dict
    second_ignored: dict

    def __init__(self, path_nodes: list[str]):
        super().__init__('keys', path_nodes)
        self.first_more = {}
        self.second_more = {}
        self.first_ignored = {}
        self.second_ignored = {}

    def add_first_diff(self, key, value):
        self.first_more.update({key: value})

    def add_second_diff(self, key, value):
        self.second_more.update({key: value})

    def add_first_ignored(self, key, value):
        self.first_ignored.update({key: value})

    def add_second_ignored(self, key, value):
        self.second_ignored.update({key: value})


@dataclass
class ValueDiff(JsonDiffContent):
    first_value: str
    second_value: str

    def __init__(self, path_nodes: list[str], first_value, second_value):
        super().__init__('value', path_nodes)
        self.first_value = first_value
        self.second_value = second_value


@dataclass
class TypeDiff(JsonDiffContent):
    first_type: str
    first_value: str
    second_type: str
    second_value: str

    def __init__(self, path_nodes: list[str], first_type, first_value,
                 second_type, second_value):
        super().__init__('type', path_nodes)
        self.first_value = str(first_value)
        self.first_type = str(first_type)
        self.second_value = str(second_value)
        self.second_type = str(second_type)


def all_empty(*obj):
    for item in obj:
        if isinstance(item, dict):
            if not all_empty(*item.values()):
                return False
        elif isinstance(item, list) or isinstance(item, tuple):
            if not all_empty(*item):
                return False
        else:
            if item != "":
                return False
    return True


def get_diff_keys(jkeys1, jkeys2, ingore_keys: set[str] = set()):
    jkeys_set_1 = set(jkeys1)
    jkeys_set_2 = set(jkeys2)
    # first 比 second 多的键
    first_diff_origin_keys = jkeys_set_1 - jkeys_set_2
    # second 比 first 多的键
    second_diff_origin_keys = jkeys_set_2 - jkeys_set_1
    # first忽略的键
    first_ignored_keys = set(
        [key for key in first_diff_origin_keys if key in ingore_keys])
    # second忽略的键
    second_ignored_keys = set(
        [key for key in second_diff_origin_keys if key in ingore_keys])
    # 需要后续校验的键
    check_keys = jkeys_set_1 - first_ignored_keys - first_diff_origin_keys
    return first_diff_origin_keys - first_ignored_keys, second_diff_origin_keys - second_ignored_keys, first_ignored_keys, second_ignored_keys, check_keys


def compare(
    first: str, second: str,
    ingore_keys: list[str] =[]) -> list[JsonDiffContent]:
    diff_result: list[JsonDiffContent] = []
    do_compare(json.loads(first),
               json.loads(second),
               set(ingore_keys),
               diff_result=diff_result)
    return diff_result


def do_compare(first,
               second,
               ingore_keys: set[str] = set(),
               diff_result: list[JsonDiffContent] = [],
               path_nodes=[]):
    if type(first) != type(second):
        diff_result.append(
            TypeDiff(path_nodes, type(first), first, type(second), second))
        return
    elif type(first) == dict:
        first_diff_keys, second_diff_keys, first_ignored_keys, second_ignored_keys, check_keys = get_diff_keys(
            first.keys(), second.keys(), ingore_keys)
        if first_diff_keys and second_diff_keys:
            keys_diff = KeysDiff(path_nodes=path_nodes)
            diff_result.append(keys_diff)
            for key in first_diff_keys:
                keys_diff.add_first_diff(key, first[key])
            for key in first_ignored_keys:
                keys_diff.add_first_ignored(key, first[key])
            for key in second_diff_keys:
                keys_diff.add_second_diff(key, second[key])
            for key in second_ignored_keys:
                keys_diff.add_second_ignored(key, second[key])
        if len(check_keys) > 0:
            for key in check_keys:
                do_compare(first[key],
                           second[key],
                           ingore_keys,
                           diff_result,
                           path_nodes=path_nodes + [key])
    elif type(first) == list and len(first) == len(second):
        for index in range(len(first)):
            do_compare(first[index],
                       second[index],
                       ingore_keys,
                       diff_result,
                       path_nodes=path_nodes + [str(index)])
    else:
        if str(first) != str(second) and (path_nodes[-1] not in ingore_keys):
            diff_result.append(ValueDiff(path_nodes, first, second))
